package com.sun.common.core.excel.listener;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.Head;
import com.sun.common.core.excel.BaseExcel;
import com.sun.common.core.excel.model.ErrorDataValid;
import com.sun.common.core.excel.model.SuppressWarningConstant;
import com.sun.common.core.model.dto.SimpleVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * The type Abstract valid listener.
 *
 * @param <T> the type parameter
 * @author : Milo
 */
@SuppressWarnings(SuppressWarningConstant.UNUSED)
public class AnalysisValidReadListener<T extends BaseExcel> extends AnalysisEventListener<T> {
    private static final Logger log = LoggerFactory.getLogger(AnalysisValidReadListener.class);
    private SimpleVO simpleVO;
    private final Integer batch = 1000;
    private final Consumer<List<T>> consumer;
    private final List<T> buffer = new ArrayList<>(1000);
    private final Predicate<T> exist;
    private final Validator validator;
    private Map<String, Integer> fieldColumnMapping;

    private final List<ErrorDataValid> errorDataValidList;


    /**
     * Instantiates a new Abstract valid listener.
     *
     * @param consumer the consumer
     */
    public AnalysisValidReadListener(final Predicate<T> exist, final Consumer<List<T>> consumer, final Validator validator) {
        this(exist, consumer, validator, new SimpleVO());
    }

    /**
     * Instantiates a new Jdbc event listener.
     *
     * @param consumer  the consumer
     * @param validator the validator
     */
    public AnalysisValidReadListener(final Predicate<T> exist, final Consumer<List<T>> consumer, final Validator validator, SimpleVO simpleVO) {
        this.validator = validator;
        this.consumer = consumer;
        this.exist = exist;
        this.errorDataValidList = new ArrayList<>(batch);
        this.simpleVO = simpleVO;
    }

    @Override
    public void invoke(final T data, final AnalysisContext context) {
        // 先进行 validate 校验 校验通过之后 加入临时对象中 用于批量的数据库层面的校验
        if (this.validate(data, context)) {
            this.buffer.add(data);
        }

        // 批量校验
        if (this.buffer.size() >= this.batch) {
            this.consumer.accept(this.buffer);
            this.buffer.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(final AnalysisContext context) {
        if (!this.buffer.isEmpty()) {
            this.consumer.accept(this.buffer);
            this.buffer.clear();
        }
    }

    /**
     * Valid.
     *
     * @param data    the data
     * @param context the context
     */
    protected final boolean validate(final T data, final AnalysisContext context) {
        final Set<ConstraintViolation<T>> validate = this.validator.validate(data, Default.class);
        if (CollUtil.isNotEmpty(validate)) {
            validate.forEach(v -> {
                final String field = v.getPropertyPath().toString();
                final String message = v.getMessage();
                final Object value = v.getInvalidValue();
                final Integer rowIndex = context.readRowHolder().getRowIndex() + 1;
                final Integer columnIndex = this.getColumnIndex(field, context) + 1;
                if (log.isWarnEnabled()) {
                    log.warn("Validate error.Field :[{}],message:[{}],value:[{}]", field, message, value);
                }

                // 将表格中所有的错误收集一下，到时候同时返回给前台
                ErrorDataValid errorDataValid = new ErrorDataValid(rowIndex, columnIndex, message, field, value);
                errorDataValidList.add(errorDataValid);
            });
            return false;
        }
        return true;
    }

    /**
     * 听过属性从context获取列号
     *
     * @param field   属性名称
     * @param context excel上下文
     * @return 列号
     */
    private Integer getColumnIndex(final String field, final AnalysisContext context) {
        if (Objects.nonNull(this.fieldColumnMapping)) {
            return this.fieldColumnMapping.get(field);
        }
        synchronized (this) {
            if (Objects.nonNull(this.fieldColumnMapping)) {
                return this.fieldColumnMapping.get(field);
            }
            this.fieldColumnMapping = new HashMap<>(16);
            final Map<Integer, Head> excelFileHead = context.readSheetHolder().excelReadHeadProperty().getHeadMap();
            for (final Map.Entry<Integer, Head> entry : excelFileHead.entrySet()) {
                this.fieldColumnMapping.put(entry.getValue().getFieldName(), entry.getKey());
            }
            return this.fieldColumnMapping.get(field);
        }
    }

    public List<ErrorDataValid> getErrorDataValidList() {
        return errorDataValidList;
    }
}
